import React, { useRef, useEffect } from 'react';
import { Box, Button, Typography } from '@mui/material';
import {
  primaryColor,
  secondaryColor,
  bgLight,
  bgDark,
  fontColor,
} from '../../theme';

const rectMargin = 4;

interface TutorialStep {
  target: string;
  validation?: string;
  title: string;
  content: React.ReactNode;
  waitFor?: string;
  grabGlobalClick?: boolean;
  collectionIndex?: number;
}

interface TutorialBubbleProps {
  requestedTourStep: number | null;
  currentTourStep: number;
  steps: TutorialStep[];
  lastCheckRender: any;
  stopTutorial: () => void;
  incrementTutorialStep: () => void;
  validateTutorialStep: (data: { tourStep: number }) => void;
}

/**
 * TutorialBubble Component
 * Displays tutorial bubble with step information
 */
const TutorialBubble: React.FC<TutorialBubbleProps> = ({
  requestedTourStep,
  steps,
  lastCheckRender,
  stopTutorial,
  incrementTutorialStep,
  validateTutorialStep,
  currentTourStep,
}) => {
  const tutorialTarget = useRef<HTMLElement | null>(null);

  useEffect(() => () => {
    tutorialTarget.current = null;
  }, []);

  const getDOMTarget = (target: string, config?: TutorialStep): HTMLElement | null => {
    // We query the DOM with the selector
    let DOMtargets: NodeListOf<HTMLElement> | HTMLCollectionOf<HTMLElement> = document.querySelectorAll<HTMLElement>(target);

    if (!DOMtargets || DOMtargets.length === 0) {
      DOMtargets = document.getElementsByClassName(target) as HTMLCollectionOf<HTMLElement>;
      if (!DOMtargets || DOMtargets.length === 0) {
        DOMtargets = document.getElementsByTagName(target) as HTMLCollectionOf<HTMLElement>;
      }
    }

    // if there is no DOM element, we stop the tutorial
    if (!DOMtargets || DOMtargets.length === 0) {
      return null;
    }

    // we pass the element index as configuration or 0 by default
    return DOMtargets[config?.collectionIndex || 0];
  };

  function calculateVisiblePosition(rect1: DOMRect, width2: number, height2: number): { x: number; y: number } {
    const windowHeight = window.innerHeight;
    const windowWidth = window.innerWidth;

    let x = rect1.left; // bubble is aligned with the left of the rectangle
    let y = rect1.top - windowHeight + rect1.height + rectMargin;

    // Check if element2 is outside the viewport horizontally
    if (x + width2 > windowWidth) {
      x = rect1.left - (x + width2 - windowWidth + rectMargin);
    }

    if (x < 0) {
      x = rect1.left + rect1.width + 2 * rectMargin;
      y = rect1.top - windowHeight - rect1.height - rectMargin;
    }

    // Check if element2 is outside the viewport vertically
    if (y + height2 > windowHeight) {
      y = rect1.top - windowHeight + rect1.height - height2;
    }

    return { x, y };
  }

  if (!requestedTourStep || requestedTourStep < 1 || requestedTourStep > steps.length) {
    return null;
  }

  const tourStep = steps[requestedTourStep - 1];
  if (!tourStep) {
    return null;
  }

  const nextTourStep = steps[requestedTourStep];
  const { target: nextTarget } = nextTourStep || { target: undefined };

  const { target, validation, title, content } = tourStep;

  const DOMtarget = getDOMTarget(target, tourStep);
  const nextDOMtarget = nextTarget ? getDOMTarget(nextTarget, nextTourStep) : null;
  const visible = DOMtarget?.checkVisibility();

  if (!visible || !DOMtarget) {
    return <></>;
  }

  const isEditable = (el: HTMLElement | null): boolean => {
    if (el && ['input', 'textarea'].includes(el.tagName.toLowerCase())) {
      const inputEl = el as HTMLInputElement | HTMLTextAreaElement;
      return !inputEl.readOnly && !inputEl.disabled;
    }
    const selection = window.getSelection();
    const anchorNode = selection?.anchorNode;
    if (!anchorNode) {
      return false;
    }
    const parentNode = anchorNode.parentNode as HTMLElement;
    return parentNode?.isContentEditable || false;
  };

  const isAnyEditable = (element: HTMLElement): boolean => {
    if (isEditable(element)) {
      return true;
    }

    for (let i = 0; i < element.children.length; i++) {
      const child = element.children[i] as HTMLElement;
      if (isAnyEditable(child)) {
        return true;
      }
    }
    return false;
  };

  const listen = (): void => {
    tutorialTarget.current = null;
    incrementTutorialStep();
  };

  const stop = (): void => {
    tutorialTarget.current = null;
    stopTutorial();
  };

  const isNextDisabled = (validationExpr?: string, isDisabled?: boolean, startElement?: HTMLElement): boolean => {
    if (validationExpr && startElement) {
      if ((startElement as HTMLInputElement)?.value === validationExpr) {
        return !isDisabled;
      }
      try {
        const validationDOMTarget =
          startElement.querySelector(validationExpr) ||
          (startElement.parentNode as HTMLElement)?.querySelector(validationExpr);
        if (validationDOMTarget && validationDOMTarget.checkVisibility()) {
          return !isDisabled;
        }
      } catch (e) {
        // In case the validation expr is not selector
      }
      return true;
    }
    return !isDisabled;
  };

  if (currentTourStep === requestedTourStep) {
    tutorialTarget.current = null;
  }

  if (!tutorialTarget.current && currentTourStep !== requestedTourStep) {
    tutorialTarget.current = DOMtarget;
    let { waitFor, grabGlobalClick } = tourStep;
    if (!waitFor) {
      waitFor = isAnyEditable(DOMtarget) ? 'fieldEdition' : 'click';
    }
    switch (waitFor) {
      case 'click':
        const nextAction = requestedTourStep === steps.length ? stop : listen;
        const listenerTarget = grabGlobalClick ? document : DOMtarget;
        listenerTarget.addEventListener('click', nextAction as EventListener, {
          once: true,
          capture: true,
        });
        break;
      case 'fieldEdition':
        // Do nothing, we wait for a click on "next"
        break;
      default:
        break;
    }
  }

  const targetRect = DOMtarget.getBoundingClientRect();
  const { x, y } = calculateVisiblePosition(targetRect, 400, 184);

  const hasOtherSteps = requestedTourStep < steps.length;
  const nextIsVisible = nextDOMtarget?.checkVisibility();

  return (
    <Box className="tutorials">
      <Box
        className="tutorials_highlight"
        id="tutorialTargetRectangle"
        sx={{
          position: 'fixed',
          top: targetRect.top - rectMargin,
          left: targetRect.left - rectMargin,
          width: targetRect.width + 2 * rectMargin,
          height: targetRect.height + 2 * rectMargin,
          border: `2px solid ${primaryColor}`,
          borderRadius: '4px',
          pointerEvents: 'none',
          zIndex: 9999,
        }}
      />

      <Box className="tutorials_wrapper" id="tutorialBubble">
        <Box
          className="tutorials_content"
          sx={{
            position: 'fixed',
            top: y,
            left: x,
            width: 400,
            minHeight: 184,
            backgroundColor: bgDark,
            color: fontColor,
            padding: 2,
            borderRadius: 1,
            boxShadow: '0 4px 20px rgba(0,0,0,0.3)',
            zIndex: 10000,
          }}
        >
          <Typography component="h3" variant="h6">
            {title}
          </Typography>

          <Box sx={{ marginTop: 1 }}>{content}</Box>

          <Box pt={2.5} display="flex" alignItems="center" justifyContent="space-between">
            <Typography>
              {requestedTourStep} / {steps.length}
            </Typography>

            <Box display="flex" alignItems="center" gap={1}>
              <Button onClick={stop} color="primary">
                {hasOtherSteps ? 'Skip' : 'Close'}
              </Button>

              {hasOtherSteps && (
                <Button
                  variant="contained"
                  color="primary"
                  onClick={listen}
                  disabled={isNextDisabled(validation, !nextIsVisible, DOMtarget)}
                >
                  Next
                </Button>
              )}
            </Box>
          </Box>
        </Box>
      </Box>
    </Box>
  );
};

export default TutorialBubble;

